package org.jflame.commons.crypto.digest;

import org.jflame.commons.codec.Transcoder;
import org.jflame.commons.crypto.BC;
import org.jflame.commons.crypto.EncryptException;
import org.jflame.commons.crypto.KeyUtils;
import org.jflame.commons.util.CharsetHelper;
import org.jflame.commons.util.StringHelper;

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;

/**
 * 数字签名工具类
 * 
 * @see SignatureAlg
 * @author charles.zhang
 */
public final class Signer {

    /**
     * 生成数字签名,返回Base64编码的字符串
     * 
     * @param algorithm
     * @param content 要签名的内容,UTF-8编码
     * @param privateKey base64私钥
     * @return Base64编码字符串
     * @throws SignatureException
     */
    public static String sign(SignatureAlg algorithm, String content, String privateKey) throws SignatureException {
        PrivateKey key = null;
        try {
            key = KeyUtils.loadPrivateKey(algorithm.keyAlgorithm(), privateKey);
        } catch (EncryptException e) {
            throw new SignatureException(e.getMessage());
        }
        return sign(algorithm, content, key);
    }

    /**
     * 生成数字签名,返回Base64编码的字符串
     * 
     * @param algorithm
     * @param content 要签名的内容,UTF-8编码
     * @param privateKey 私钥
     * @return Base64编码字符串
     * @throws SignatureException
     */
    public static String sign(SignatureAlg algorithm, String content, PrivateKey privateKey) throws SignatureException {
        byte[] contentBytes = CharsetHelper.getUtf8Bytes(content);
        return Transcoder.encodeBase64URLSafeString(sign(algorithm.name(), contentBytes, privateKey));
    }

    /**
     * 生成数字签名,返回Base64编码的字符串
     * 
     * @see SignatureAlg
     * @param algorithm 签名算法名称, 参考SignatureAlgorithm
     * @param content 要签名的内容,UTF-8编码
     * @param privateKey base64私钥
     * @return Base64编码字符串
     * @throws SignatureException
     */
    public static String sign(String algorithm, String content, String privateKey) throws SignatureException {
        PrivateKey key;
        try {
            key = KeyUtils.loadPrivateKey(toKeyAlg(algorithm), privateKey);
        } catch (EncryptException e) {
            throw new SignatureException(e.getMessage());
        }
        byte[] contentBytes = CharsetHelper.getUtf8Bytes(content);
        byte[] resultBytes = sign(algorithm, contentBytes, key);
        return Transcoder.encodeBase64URLSafeString(resultBytes);
    }

    /**
     * 生成数字签名
     * <p>
     * 常用算法参考:{@link SignatureAlg}, 可用算法名查看: {@link <a href=
     * "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#Signature">官方文档</a> }
     * 
     * @param algorithm 签名算法名称
     * @param content 待签名内容
     * @param privateKey 签名私钥
     * @return 签名结果
     */
    public static byte[] sign(String algorithm, byte[] content, PrivateKey privateKey) throws SignatureException {
        Signature signature;

        try {
            if (StringHelper.indexOfIgnoreCase(algorithm, "withSM2") > 1) {
                signature = Signature.getInstance(algorithm, BC.addProvider());
            } else {
                signature = Signature.getInstance(algorithm);
            }
            signature.initSign(privateKey);
            signature.update(content);
            return signature.sign();
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            throw new SignatureException("生成签名异常", e);
        } catch (EncryptException e) {
            throw new SignatureException(e.getMessage());
        }
    }

    /**
     * 验证签名
     * <p>
     * 常用算法参考:{@link SignatureAlg}, 可用算法名查看: {@link <a href=
     * "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#Signature">官方文档</a> }
     * 
     * @param algorithm 签名算法名
     * @param content 待验证内容
     * @param sign 原签名
     * @param publicKey 公钥
     * @return 验证通过返回true
     */
    public static boolean verify(String algorithm, byte[] content, byte[] sign, PublicKey publicKey) {
        try {
            Signature signature = Signature.getInstance(algorithm);
            signature.initVerify(publicKey);
            signature.update(content);
            return signature.verify(sign);
        } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 验证签名
     * 
     * @param algorithm 签名算法
     * @param content 待验证内容
     * @param signBase64Str 原签名
     * @param publicKey 公钥(base64字符串)
     * @return 验证通过返回true
     */
    public static boolean verify(SignatureAlg algorithm, String content, String signBase64Str, String publicKey) {
        return verify(algorithm.name(), content, signBase64Str, publicKey);
    }

    /**
     * 验证签名
     * 
     * @param algorithm 签名算法
     * @param content 待验证内容
     * @param signBase64Str 原签名
     * @param publicKey 公钥
     * @return 验证通过返回true
     */
    public static boolean verify(SignatureAlg algorithm, String content, String signBase64Str, PublicKey publicKey) {
        byte[] contentBytes = CharsetHelper.getUtf8Bytes(content);
        byte[] signBytes = Transcoder.decodeBase64(signBase64Str);

        return verify(algorithm.name(), contentBytes, signBytes, publicKey);
    }

    /**
     * 验证签名
     * 
     * @param algorithm 签名算法
     * @param content 待验证内容
     * @param signBase64Str 原签名 Base64字符串
     * @param publicKey 公钥(base64字符串)
     * @return 验证通过返回true
     */
    public static boolean verify(String algorithm, String content, String signBase64Str, String publicKey) {
        PublicKey key;
        try {
            key = KeyUtils.loadPublicKey(toKeyAlg(algorithm), publicKey);
        } catch (EncryptException e) {
            throw new SecurityException(e);
        }

        byte[] contentBytes = CharsetHelper.getUtf8Bytes(content);
        byte[] signBytes = Transcoder.decodeBase64(signBase64Str);

        return verify(algorithm, contentBytes, signBytes, key);
    }

    private static String toKeyAlg(String algorithm) {
        String keyAlg = StringHelper.substringAfter(algorithm, "with");
        if ("ECDSA".equalsIgnoreCase(keyAlg) || "SM2".equalsIgnoreCase(keyAlg)) {
            keyAlg = "EC";
        }
        return keyAlg;
    }

}
